﻿using System.Linq;
using System.Collections.Generic;

using Godot.core.io;
using Godot.core;

namespace Godot.modules.gdscript
{
    public partial class GDCompiler
    {
        protected bool _create_unary_operator(ref CodeGen codegen, GDParser.OperatorNode on, Variant.Operator op, int p_stack_level) 
        {
            if(on.arguments.Count != 1)
                return false;

	        int src_address_a = _parse_expression(ref codegen, on.arguments[0], p_stack_level);
	        if (src_address_a < 0)
		        return false;

	        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_OPERATOR); // perform operator
	        codegen.opcodes.Add((int)op); //which operator
	        codegen.opcodes.Add(src_address_a); // argument 1
	        codegen.opcodes.Add((int)GDFunction.Address.ADDR_TYPE_NIL); // argument 2 (unary only takes one parameter)
	        return true;
        }

        protected bool _create_binary_operator(ref CodeGen codegen, GDParser.OperatorNode on, Variant.Operator op, int p_stack_level) 
        {
            if(on.arguments.Count != 2)
                return false;

	        int src_address_a = _parse_expression(ref codegen, on.arguments[0], p_stack_level);
	        
            if (src_address_a < 0)
		        return false;

	        if ((src_address_a & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0)
		        p_stack_level++; //uses stack for return, increase stack

	        int src_address_b = _parse_expression(ref codegen, on.arguments[1], p_stack_level);
	        if (src_address_b < 0)
		        return false;


	        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_OPERATOR); // perform operator
	        codegen.opcodes.Add((int)op); //which operator
	        codegen.opcodes.Add(src_address_a); // argument 1
	        codegen.opcodes.Add(src_address_b); // argument 2 (unary only takes one parameter)
	        return true;
        }

        protected Error _parse_block(ref CodeGen codegen, GDParser.BlockNode p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1)
        {
            codegen.push_stack_identifiers();
            int new_identifiers = 0;
            codegen.current_line = p_block.line;

            for (int i = 0; i < p_block.statements.Count; i++)
            {
                GDParser.Node s = p_block.statements[i];

                switch (s.type)
                {
                    case GDParser.Node.Type.TYPE_NEWLINE: {
				        GDParser.NewLineNode nl = (GDParser.NewLineNode)(s);
				        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_LINE);
				        codegen.opcodes.Add(nl.line);
				        codegen.current_line = nl.line;
			        } break;

                    case GDParser.Node.Type.TYPE_CONTROL_FLOW: {
                        GDParser.ControlFlowNode cf = (GDParser.ControlFlowNode)(s);
                        switch(cf.cf_type) 
                        {
					        case GDParser.ControlFlowNode.CFType.CF_IF: {
                                int ret = _parse_expression(ref codegen, cf.arguments[0], p_stack_level, false);

                                if (ret<0)
							        return Error.ERR_PARSE_ERROR;

						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP_IF_NOT);
						        codegen.opcodes.Add(ret);
						        int else_addr = codegen.opcodes.Count;
						        codegen.opcodes.Add(0); //temporary

                                Error err = _parse_block(ref codegen,cf.body, p_stack_level,p_break_addr,p_continue_addr);
						        if (err != Error.OK)
							        return err;

						        if (cf.body_else != null) 
                                {
							        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP);
							        int end_addr = codegen.opcodes.Count;
							        codegen.opcodes.Add(0);
							        codegen.opcodes[else_addr] = codegen.opcodes.Count;

							        err = _parse_block(ref codegen, cf.body_else, p_stack_level,p_break_addr,p_continue_addr);
							        if (err != Error.OK)
								        return err;

							        codegen.opcodes[end_addr] = codegen.opcodes.Count;
						        } 
                                else 
                                {
							        //end without else
							        codegen.opcodes[else_addr] = codegen.opcodes.Count;
						        }
                            } break;

                            case GDParser.ControlFlowNode.CFType.CF_FOR: {
                                int slevel = p_stack_level;
						        int iter_stack_pos = slevel;
						        int iterator_pos = (slevel++) | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);
						        int counter_pos = (slevel++) | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);
						        int container_pos = (slevel++) | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);
                                codegen.alloc_stack(slevel);
                                codegen.push_stack_identifiers();
                                codegen.add_stack_identifier(((GDParser.IdentifierNode)cf.arguments[0]).name, iter_stack_pos);

                                int ret = _parse_expression(ref codegen,cf.arguments[1], slevel, false);
						        if (ret < 0)
							        return Error.ERR_COMPILATION_FAILED;

						        //assign container
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ASSIGN);
						        codegen.opcodes.Add(container_pos);
						        codegen.opcodes.Add(ret);

						        //begin loop
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ITERATE_BEGIN);
						        codegen.opcodes.Add(counter_pos);
						        codegen.opcodes.Add(container_pos);
						        codegen.opcodes.Add(codegen.opcodes.Count + 4);
						        codegen.opcodes.Add(iterator_pos);
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP); //skip code for next
						        codegen.opcodes.Add(codegen.opcodes.Count + 8);
						        //break loop
						        int break_pos = codegen.opcodes.Count;
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP); //skip code for next
						        codegen.opcodes.Add(0); //skip code for next
						        //next loop
						        int continue_pos = codegen.opcodes.Count;
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ITERATE);
						        codegen.opcodes.Add(counter_pos);
						        codegen.opcodes.Add(container_pos);
						        codegen.opcodes.Add(break_pos);
						        codegen.opcodes.Add(iterator_pos);


						        Error err = _parse_block(ref codegen, cf.body, slevel, break_pos, continue_pos);
						        if (err != Error.OK)
							        return err;


						        codegen.opcodes.Add((int) GDFunction.Opcode.OPCODE_JUMP);
						        codegen.opcodes.Add(continue_pos);
						        codegen.opcodes[break_pos + 1] = codegen.opcodes.Count;

						        codegen.pop_stack_identifiers();
                            } break;

                            case GDParser.ControlFlowNode.CFType.CF_WHILE: {
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP);
						        codegen.opcodes.Add((int)codegen.opcodes.Count + 3);
						        int break_addr = codegen.opcodes.Count;
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP);
						        codegen.opcodes.Add(0);
						        int continue_addr = codegen.opcodes.Count;

						        int ret = _parse_expression(ref codegen,cf.arguments[0], p_stack_level,false);
						        if (ret < 0)
							        return Error.ERR_PARSE_ERROR;
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP_IF_NOT);
						        codegen.opcodes.Add(ret);
						        codegen.opcodes.Add(break_addr);
						        Error err = _parse_block(ref codegen,cf.body, p_stack_level ,break_addr,continue_addr);
						        if (err != Error.OK)
							        return err;
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP);
						        codegen.opcodes.Add(continue_addr);

						        codegen.opcodes[break_addr + 1] = codegen.opcodes.Count;

					        } break;

                            case GDParser.ControlFlowNode.CFType.CF_SWITCH: {
                                //none
					        } break;

                            case GDParser.ControlFlowNode.CFType.CF_BREAK: {
						        if (p_break_addr < 0) 
                                {
							        _set_error("'break'' not within loop",cf);
							        return Error.ERR_COMPILATION_FAILED;
						        }
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP);
						        codegen.opcodes.Add(p_break_addr);
					        } break;

					        case GDParser.ControlFlowNode.CFType.CF_CONTINUE: {
						        if (p_continue_addr<0) {

							        _set_error("'continue' not within loop",cf);
							        return Error.ERR_COMPILATION_FAILED;
						        }
						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP);
						        codegen.opcodes.Add(p_continue_addr);

					        } break;

                            case GDParser.ControlFlowNode.CFType.CF_RETURN: {
						        int ret;
						        if (cf.arguments.Count != 0) 
                                {
							        ret = _parse_expression(ref codegen,cf.arguments[0], p_stack_level,false);
							        if (ret < 0)
								        return Error.ERR_PARSE_ERROR;

						        } 
                                else 
                                {
							        ret = (int)GDFunction.Address.ADDR_TYPE_NIL << (int)GDFunction.Address.ADDR_BITS;
						        }

						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_RETURN);
						        codegen.opcodes.Add(ret);
					        } break;
                        };//___end switch(cf.cf_type) 
                    }break;

                    case GDParser.Node.Type.TYPE_ASSERT: {
				        // try subblocks
				        GDParser.AssertNode asn = (GDParser.AssertNode)(s);

				        int ret = _parse_expression(ref codegen,asn.condition, p_stack_level,false);
				        if (ret < 0)
					        return Error.ERR_PARSE_ERROR;

				        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ASSERT);
				        codegen.opcodes.Add(ret);
			        } break;

			        case GDParser.Node.Type.TYPE_LOCAL_VAR: {
				        GDParser.LocalVarNode lv = (GDParser.LocalVarNode)(s);
				        codegen.add_stack_identifier(lv.name, p_stack_level++);
				        codegen.alloc_stack(p_stack_level);
				        new_identifiers++;
			        } break;

			        default: {
				        //expression
				        int ret = _parse_expression(ref codegen, s, p_stack_level,true);
				        if (ret < 0)
					        return Error.ERR_PARSE_ERROR;
			        } break;
                }
            }
            codegen.pop_stack_identifiers();
            return Error.OK;
        }
   
        protected int _parse_expression(ref CodeGen codegen, GDParser.Node p_expression, int p_stack_level, bool p_root = false)
        {
            switch (p_expression.type)
            {
                //should parse variable declaration and adjust stack accordingly...
		        case GDParser.Node.Type.TYPE_IDENTIFIER: {
			        //return identifier
			        //wait, identifier could be a local variable or something else... careful here, must reference properly
			        //as stack may be more interesting to work with

			        //This could be made much simpler by just indexing "self", but done this way (with custom self-addressing modes) increases peformance a lot.

			        GDParser.IdentifierNode idn = (GDParser.IdentifierNode)(p_expression);

			        StringName identifier = idn.name;

			        // TRY STACK!
			        if (codegen.stack_identifiers.ContainsKey(identifier)) 
                    {
				        int pos = codegen.stack_identifiers[identifier];
				        return pos | ((int)GDFunction.Address.ADDR_TYPE_STACK_VARIABLE << (int)GDFunction.Address.ADDR_BITS);
			        }

			        //TRY ARGUMENTS!
			        if (codegen.function_node != null || !codegen.function_node.is_static) 
                    {
				        // TRY MEMBER VARIABLES!

				        //static function
				        if (codegen.script._member_indices.ContainsKey(identifier)) 
                        {
					        int idx = codegen.script._member_indices[identifier];
					        return idx | ((int)GDFunction.Address.ADDR_TYPE_MEMBER << (int)GDFunction.Address.ADDR_BITS); //argument (stack root)
				        }
			        }

			        //TRY CLASS CONSTANTS
			        GDScript scr = codegen.script;
			        GDNativeClass nc = null;
			        while(scr  != null) 
                    {
				        if (scr._constants.ContainsKey(identifier)) 
                        {
					        //int idx=scr->constants[identifier];
					        int idx = codegen.get_name_map_pos(identifier);
					        return idx | ((int)GDFunction.Address.ADDR_TYPE_CLASS_CONSTANT << (int)GDFunction.Address.ADDR_BITS); //argument (stack root)
				        }
				        if (scr._native != null)
					        nc = scr._native;
				        scr = scr._base;
			        }

			        // CLASS C++ Integer Constant
			        if (nc != null) 
                    {
				        bool success = false;
				        int constant = ObjectTypeDB.get_integer_constant(nc.name, identifier, ref success);
				        if (success) 
                        {
					        Variant key = constant;
					        int idx;

					        if (!codegen.constant_map.ContainsKey(key)) 
                            {
						        idx = codegen.constant_map.Count;
						        codegen.constant_map[key] = idx;
					        } 
                            else 
                            {
						        idx = codegen.constant_map[key];
					        }

					        return idx |((int)GDFunction.Address.ADDR_TYPE_LOCAL_CONSTANT << (int)GDFunction.Address.ADDR_BITS); //make it a local constant (faster access)
				        }
			        }

			        if (codegen.script._subclasses.ContainsKey(identifier)) 
                    {
				        //same with a subclass, make it a local constant.
				        int idx = codegen.get_constant_pos(codegen.script._subclasses[identifier]);
				        return idx |((int)GDFunction.Address.ADDR_TYPE_LOCAL_CONSTANT << (int)GDFunction.Address.ADDR_BITS); //make it a local constant (faster access)
			        }

			        if (GDScriptLanguage.singleton.get_global_map().ContainsKey(identifier))
                    {
				        int idx = GDScriptLanguage.singleton.get_global_map()[identifier];
				        return idx | ((int)GDFunction.Address.ADDR_TYPE_GLOBAL << (int)GDFunction.Address.ADDR_BITS); //argument (stack root)
			        }

			        //not found, error
			        _set_error("Identifier not found: " + identifier, p_expression);
			        return -1;
		        } break;

                case GDParser.Node.Type.TYPE_CONSTANT: {
			        //return constant
			        GDParser.ConstantNode cn = (GDParser.ConstantNode)(p_expression);

			        int idx;

			        if (!codegen.constant_map.ContainsKey(cn.value)) 
                    {
				        idx = codegen.constant_map.Count;
				        codegen.constant_map[cn.value] = idx;

			        } 
                    else 
                    {
				        idx=codegen.constant_map[cn.value];
			        }


			        return idx | ((int)GDFunction.Address.ADDR_TYPE_LOCAL_CONSTANT << (int)GDFunction.Address.ADDR_BITS); //argument (stack root)

		        } break;

                case GDParser.Node.Type.TYPE_SELF: {
			        //return constant
			        if (codegen.function_node != null && codegen.function_node.is_static) 
                    {
				        _set_error("'self' not present in static function!",p_expression);
				        return -1;
			        }
			        return ((int)GDFunction.Address.ADDR_TYPE_SELF << (int)GDFunction.Address.ADDR_BITS);
		        } break;

                case GDParser.Node.Type.TYPE_ARRAY: {
			        GDParser.ArrayNode an = (GDParser.ArrayNode)(p_expression);
			        List<int> values = new List<int>();

			        int slevel = p_stack_level;

			        for(int i = 0; i < an.elements.Count; i++) 
                    {
				        int ret = _parse_expression(ref codegen, an.elements[i], slevel);
				        if (ret < 0)
					        return ret;
				        if ((ret & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                        {
					        slevel++;
					        codegen.alloc_stack(slevel);
				        }

				        values.Add(ret);
			        }

			        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_CONSTRUCT_ARRAY);
			        codegen.opcodes.Add(values.Count);
			        for(int i = 0; i < values.Count; i++)
				        codegen.opcodes.Add(values[i]);

			        int dst_addr = (p_stack_level) | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);
			        codegen.opcodes.Add(dst_addr); // append the stack level as destination address of the opcode
			        codegen.alloc_stack(p_stack_level);
			        return dst_addr;

		        } break;
    
                case GDParser.Node.Type.TYPE_DICTIONARY: {
			        GDParser.DictionaryNode dn = (GDParser.DictionaryNode)(p_expression);
			        List<int> values = new List<int>();

			        int slevel=p_stack_level;

			        for(int i = 0; i < dn.elements.Count; i++) 
                    {
				        int ret = _parse_expression(ref codegen, dn.elements[i].Key, slevel);
				        if (ret < 0)
					        return ret;
				        if ((ret & ((int) GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                        {
					        slevel++;
					        codegen.alloc_stack(slevel);
				        }

				        values.Add(ret);

				        ret = _parse_expression(ref codegen, dn.elements[i].Value, slevel);
				        if (ret < 0)
					        return ret;
				        
                        if ((ret & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                        {
					        slevel++;
					        codegen.alloc_stack(slevel);
				        }

				        values.Add(ret);
			        }

			        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_CONSTRUCT_DICTIONARY);
			        codegen.opcodes.Add(dn.elements.Count);
			        for(int i = 0; i < values.Count; i++)
				        codegen.opcodes.Add(values[i]);

			        int dst_addr = (p_stack_level) | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);
			        codegen.opcodes.Add(dst_addr); // append the stack level as destination address of the opcode
			        codegen.alloc_stack(p_stack_level);
			        return dst_addr;

		        } break;

                case GDParser.Node.Type.TYPE_OPERATOR: {
			        //hell breaks loose
			        GDParser.OperatorNode on = (GDParser.OperatorNode)(p_expression);
			        switch(on.op) 
                    {
                        case GDParser.OperatorNode.Operator.OP_PARENT_CALL: {
					        if(on.arguments.Count < 1)
                                return -1;

					        GDParser.IdentifierNode idn = (GDParser.IdentifierNode)on.arguments[0];

					        List<int> arguments =  new List<int>();
					        int slevel = p_stack_level;
					        for(int i = 1; i < on.arguments.Count; i++) 
                            {
						        int ret = _parse_expression(ref codegen, on.arguments[i], slevel);
						        if (ret < 0)
							        return ret;
						        if ((ret & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                {
							        slevel++;
							        codegen.alloc_stack(slevel);
						        }
						        arguments.Add(ret);
					        }

					        //push call bytecode
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_CALL_SELF_BASE); // basic type constructor

					        codegen.opcodes.Add(codegen.get_name_map_pos(idn.name)); //instance
					        codegen.opcodes.Add(arguments.Count); //argument count
					        codegen.alloc_call(arguments.Count);
					        for(int i = 0; i < arguments.Count; i++)
						        codegen.opcodes.Add(arguments[i]); //arguments

                        } break;

                        case GDParser.OperatorNode.Operator.OP_CALL: {
                            if (on.arguments[0].type == GDParser.Node.Type.TYPE_TYPE)
                            {
                                //construct a basic type
						        if(on.arguments.Count < 1)
                                    return -1;

						        GDParser.TypeNode tn = (GDParser.TypeNode)on.arguments[0];
						        int vtype = (int)tn.vtype;

						        List<int> arguments = new List<int>();
						        int slevel = p_stack_level;
						        for(int i = 1; i < on.arguments.Count; i++) 
                                {
							        int ret = _parse_expression(ref codegen, on.arguments[i], slevel);
							        if (ret < 0)
								        return ret;
							        if ((ret & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                    {
								        slevel++;
								        codegen.alloc_stack(slevel);
							        }
							        arguments.Add(ret);

                                    //push call bytecode
						            codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_CONSTRUCT); // basic type constructor
						            codegen.opcodes.Add(vtype); //instance
						            codegen.opcodes.Add(arguments.Count); //argument count
						            codegen.alloc_call(arguments.Count);
						            for(i = 0; i < arguments.Count; i++)
							            codegen.opcodes.Add(arguments[i]); //arguments
						        }
                            }
                            else if (on.arguments[0].type == GDParser.Node.Type.TYPE_BUILT_IN_FUNCTION) 
                            {
						        //built in function
						        if(on.arguments.Count < 1)
                                    return -1;

						        List<int> arguments = new List<int>();
						        int slevel = p_stack_level;
						        for(int i = 1; i < on.arguments.Count; i++) 
                                {
							        int ret = _parse_expression(ref codegen, on.arguments[i],slevel);
							        if (ret < 0)
								        return ret;

							        if ((ret & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                    {
								        slevel++;
								        codegen.alloc_stack(slevel);
							        }

							        arguments.Add(ret);
						        }

						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_CALL_BUILT_IN);
						        codegen.opcodes.Add((int)((GDParser.BuiltInFunctionNode)on.arguments[0]).function);
						        codegen.opcodes.Add(on.arguments.Count - 1);
						        codegen.alloc_call(on.arguments.Count - 1);
						        for(int i = 0; i < arguments.Count; i++)
							        codegen.opcodes.Add(arguments[i]);
					        }
                            else 
                            {
						        //regular function
						        if(on.arguments.Count < 2)
                                    return -1;

						        GDParser.Node instance = on.arguments[0];

						        if (instance.type == GDParser.Node.Type.TYPE_SELF) 
                                {
							        //room for optimization
						        }


						        List<int> arguments = new List<int>();
						        int slevel = p_stack_level;

						        for(int i = 0; i < on.arguments.Count; i++) 
                                {
							        int ret;

							        if (i == 1) 
                                    {
								        if (on.arguments[i].type != GDParser.Node.Type.TYPE_IDENTIFIER) 
                                        {
									        _set_error("Attempt to call a non-identifier.",on);
									        return -1;
								        }

								        GDParser.IdentifierNode id = (GDParser.IdentifierNode)(on.arguments[i]);
								        ret = codegen.get_name_map_pos(id.name);

							        } 
                                    else 
                                    {
								        ret = _parse_expression(ref codegen, on.arguments[i], slevel);
								        if (ret < 0)
									        return ret;

								        if ((ret & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                        {
									        slevel++;
									        codegen.alloc_stack(slevel);
								        }
							        }
							        arguments.Add(ret);
						        }

						        codegen.opcodes.Add(p_root ? (int)GDFunction.Opcode.OPCODE_CALL : (int)GDFunction.Opcode.OPCODE_CALL_RETURN); // perform operator
						        codegen.opcodes.Add(on.arguments.Count - 2);
						        codegen.alloc_call(on.arguments.Count - 2);
						        for(int i = 0; i < arguments.Count; i++)
							        codegen.opcodes.Add(arguments[i]);
					        }
                        } break;

                        //indexing operator
				        case GDParser.OperatorNode.Operator.OP_INDEX:
				        
                        case GDParser.OperatorNode.Operator.OP_INDEX_NAMED: {

					        if(on.arguments.Count != 2)
                                return -1;

					        int slevel = p_stack_level;
					        bool named = (on.op == GDParser.OperatorNode.Operator.OP_INDEX_NAMED);

					        int from = _parse_expression(ref codegen,on.arguments[0], slevel);
					        if (from < 0)
						        return from;

					        int index;
					        if (named) 
                            {
						        index = codegen.get_name_map_pos(((GDParser.IdentifierNode)on.arguments[1]).name);

					        } 
                            else 
                            {
						        if (on.arguments[1].type == GDParser.Node.Type.TYPE_CONSTANT && ((GDParser.ConstantNode)on.arguments[1]).value.type == Variant.Type.STRING) {
							        //also, somehow, named (speed up anyway)
							        StringName name = ((GDParser.ConstantNode)on.arguments[1]).value;
							        index = codegen.get_name_map_pos(name);
							        named = true;

						        } 
                                else 
                                {
							        //regular indexing
							        if ((from & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                    {
								        slevel++;
								        codegen.alloc_stack(slevel);
							        }

							        index = _parse_expression(ref codegen, on.arguments[1],slevel);
							        if (index < 0)
								        return index;
						        }
					        }

					        codegen.opcodes.Add(named ?(int) GDFunction.Opcode.OPCODE_GET_NAMED : (int)GDFunction.Opcode.OPCODE_GET); // perform operator
					        codegen.opcodes.Add(from); // argument 1
					        codegen.opcodes.Add(index); // argument 2 (unary only takes one parameter)

				        } break;

                        case GDParser.OperatorNode.Operator.OP_AND: {

					        // AND operator with early out on failure
					        int res = _parse_expression(ref codegen, on.arguments[0], p_stack_level);
					        if (res < 0)
						        return res;
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP_IF_NOT);
					        codegen.opcodes.Add(res);
					        int jump_fail_pos = codegen.opcodes.Count;
					        codegen.opcodes.Add(0);

					        res = _parse_expression(ref codegen, on.arguments[1], p_stack_level);
					        if (res < 0)
						        return res;

					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP_IF_NOT);
					        codegen.opcodes.Add(res);
					        int jump_fail_pos2 = codegen.opcodes.Count;
					        codegen.opcodes.Add(0);

					        codegen.alloc_stack(p_stack_level); //it will be used..
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ASSIGN_TRUE);
					        codegen.opcodes.Add(p_stack_level | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS));
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP);
					        codegen.opcodes.Add(codegen.opcodes.Count + 3);
					        codegen.opcodes[jump_fail_pos] = codegen.opcodes.Count;
					        codegen.opcodes[jump_fail_pos2] = codegen.opcodes.Count;
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ASSIGN_FALSE);
					        codegen.opcodes.Add(p_stack_level | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS));
					        return p_stack_level | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);

				        } break;

                        case GDParser.OperatorNode.Operator.OP_OR: {
					        // OR operator with early out on success

					        int res = _parse_expression(ref codegen, on.arguments[0], p_stack_level);
					        if (res < 0)
						        return res;
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP_IF);
					        codegen.opcodes.Add(res);
					        int jump_success_pos = codegen.opcodes.Count;
					        codegen.opcodes.Add(0);

					        res = _parse_expression(ref codegen, on.arguments[1], p_stack_level);
					        if (res < 0)
						        return res;

					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP_IF);
					        codegen.opcodes.Add(res);
					        int jump_success_pos2 = codegen.opcodes.Count;
					        codegen.opcodes.Add(0);

					        codegen.alloc_stack(p_stack_level); //it will be used..
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ASSIGN_FALSE);
					        codegen.opcodes.Add(p_stack_level | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS));
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP);
					        codegen.opcodes.Add(codegen.opcodes.Count + 3);
					        codegen.opcodes[jump_success_pos] = codegen.opcodes.Count;
					        codegen.opcodes[jump_success_pos2] = codegen.opcodes.Count;
					        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ASSIGN_TRUE);
					        codegen.opcodes.Add(p_stack_level | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS));
					        return p_stack_level | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);

				        } break;

                        //unary operators
				        case GDParser.OperatorNode.Operator.OP_NEG: { 
                            if (!_create_unary_operator(ref codegen, on, Variant.Operator.OP_NEGATE, p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_NOT: { 
                            if (!_create_unary_operator(ref codegen, on, Variant.Operator.OP_NOT, p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_BIT_INVERT: { 
                            if (!_create_unary_operator(ref codegen,on, Variant.Operator.OP_BIT_NEGATE, p_stack_level)) 
                                return -1;
                        } break;
				        
                        case GDParser.OperatorNode.Operator.OP_PREINC: { } break; //?
				        case GDParser.OperatorNode.Operator.OP_PREDEC: { } break;
				        case GDParser.OperatorNode.Operator.OP_INC: { } break;
				        case GDParser.OperatorNode.Operator.OP_DEC: { } break;
                        
                        //binary operators (in precedence order)
				        case GDParser.OperatorNode.Operator.OP_IN: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_IN, p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_EQUAL: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_EQUAL, p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_NOT_EQUAL: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_NOT_EQUAL, p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_LESS: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_LESS, p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_LESS_EQUAL: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_LESS_EQUAL, p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_GREATER: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_GREATER, p_stack_level)) 
                                return -1;
                        } break;
				        
                        case GDParser.OperatorNode.Operator.OP_GREATER_EQUAL: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_GREATER_EQUAL, p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_ADD: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_ADD, p_stack_level)) 
                                return -1;
                        } break;
				        
                        case GDParser.OperatorNode.Operator.OP_SUB: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_SUBSTRACT, p_stack_level)) 
                                return -1;
                        } break;
				        
                        case GDParser.OperatorNode.Operator.OP_MUL: { 
                            if (!_create_binary_operator(ref codegen,on, Variant.Operator.OP_MULTIPLY, p_stack_level)) 
                                return -1;
                        } break;
				        
                        case GDParser.OperatorNode.Operator.OP_DIV: { 
                            if (!_create_binary_operator(ref codegen,on, Variant.Operator.OP_DIVIDE,p_stack_level)) 
                                return -1;
                        } break;

				        case GDParser.OperatorNode.Operator.OP_MOD: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_MODULE, p_stack_level)) 
                                return -1;
                        } break;
				        
                        //case GDParser::OperatorNode::OP_SHIFT_LEFT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_LEFT,p_stack_level)) return -1;} break;
				        //case GDParser::OperatorNode::OP_SHIFT_RIGHT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_RIGHT,p_stack_level)) return -1;} break;
				        
                        case GDParser.OperatorNode.Operator.OP_BIT_AND: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_BIT_AND, p_stack_level)) 
                                return -1;
                        } break;
				        
                        case GDParser.OperatorNode.Operator.OP_BIT_OR: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_BIT_OR, p_stack_level)) 
                                return -1;
                        } break;
				        
                        case GDParser.OperatorNode.Operator.OP_BIT_XOR: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_BIT_XOR, p_stack_level)) 
                                return -1;
                        } break;
                        
                        //shift
                        case GDParser.OperatorNode.Operator.OP_SHIFT_LEFT: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_SHIFT_LEFT, p_stack_level)) 
                                return -1;
                        } break;
                        
                        case GDParser.OperatorNode.Operator.OP_SHIFT_RIGHT: { 
                            if (!_create_binary_operator(ref codegen, on, Variant.Operator.OP_SHIFT_RIGHT, p_stack_level)) 
                                return -1;
                        } break;

                        case GDParser.OperatorNode.Operator.OP_EXTENDS: {
                            if (on.arguments.Count != 2)
                                return 0;

                            int slevel = p_stack_level;

                            int src_address_a = _parse_expression(ref codegen, on.arguments[0], slevel);

                            if (src_address_a < 0)
                                return -1;

                            if ((src_address_a & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0)
                                slevel++; //uses stack for return, increase stack

                            int src_address_b = _parse_expression(ref codegen, on.arguments[1], slevel);
                            if (src_address_b < 0)
                                return -1;

                            codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_EXTENDS_TEST); // perform operator
                            codegen.opcodes.Add(src_address_a); // argument 1
                            codegen.opcodes.Add(src_address_b); // argument 2 (unary only takes one parameter)

                        } break;

                        case GDParser.OperatorNode.Operator.OP_ASSIGN_ADD:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_SUB:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_MUL:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_DIV:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_MOD:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_SHIFT_LEFT:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_SHIFT_RIGHT:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_BIT_AND:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_BIT_OR:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN_BIT_XOR:
				        case GDParser.OperatorNode.Operator.OP_ASSIGN: {
                            if (on.arguments.Count != 2)
                                return -1;

                            if (on.arguments[0].type == GDParser.Node.Type.TYPE_OPERATOR 
                            && (((GDParser.OperatorNode)on.arguments[0]).op == GDParser.OperatorNode.Operator.OP_INDEX || ((GDParser.OperatorNode)on.arguments[0]).op == GDParser.OperatorNode.Operator.OP_INDEX_NAMED))
                            {
                                //SET (chained) MODE!!

						        int slevel = p_stack_level;

						        GDParser.OperatorNode op = (GDParser.OperatorNode)(on.arguments[0]);

						        /* Find chain of sets */

						        List<GDParser.OperatorNode> chain = new List<GDParser.OperatorNode>();
						        {
							        //create get/set chain
							        GDParser.OperatorNode n = op;
							        while(true) 
                                    {
								        chain.Add(n);
								        if (n.arguments[0].type != GDParser.Node.Type.TYPE_OPERATOR)
									        break;
								        n = (GDParser.OperatorNode)(n.arguments[0]);
								        if (n.op != GDParser.OperatorNode.Operator.OP_INDEX && n.op != GDParser.OperatorNode.Operator.OP_INDEX_NAMED)
									        break;
							        }
						        }

                                /* Chain of gets */

						        //get at (potential) root stack pos, so it can be returned
						        int prev_pos = _parse_expression(ref codegen, chain[chain.Count - 1].arguments[0], slevel);
						        if (prev_pos < 0)
							        return prev_pos;

						        int retval = prev_pos;

						        if ((retval & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                {
							        slevel++;
							        codegen.alloc_stack(slevel);
						        }

                                List<int> setchain = new List<int>();

                                foreach(GDParser.OperatorNode E in chain.Reverse<GDParser.OperatorNode>())
                                {
							        if (E == chain.First()) //ignore first
								        break;

							        bool named = E.op == GDParser.OperatorNode.Operator.OP_INDEX_NAMED;
							        int key_idx;

							        if (named) 
                                    {
								        key_idx = codegen.get_name_map_pos(((GDParser.IdentifierNode)E.arguments[1]).name);
							        } 
                                    else 
                                    {
								        GDParser.Node key = E.arguments[1];
								        key_idx = _parse_expression(ref codegen, key, slevel);
								        if ((retval & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                        {
									        slevel++;
									        codegen.alloc_stack(slevel);
								        }

							        }

							        if (key_idx<0)
								        return key_idx;

							        codegen.opcodes.Add(named ? (int)GDFunction.Opcode.OPCODE_GET_NAMED : (int)GDFunction.Opcode.OPCODE_GET);
							        codegen.opcodes.Add(prev_pos);
							        codegen.opcodes.Add(key_idx);
							        slevel++;
							        codegen.alloc_stack(slevel);
							        int dst_pos = ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS) | slevel;
							        codegen.opcodes.Add(dst_pos);

							        //add in reverse order, since it will be reverted
							        setchain.Add(dst_pos);
							        setchain.Add(key_idx);
							        setchain.Add(prev_pos);
							        setchain.Add(named ? (int)GDFunction.Opcode.OPCODE_SET_NAMED : (int)GDFunction.Opcode.OPCODE_SET);

							        prev_pos = dst_pos;

						        }

						        setchain.Reverse();

                                int set_index;
						        bool is_named = false;

						        if (((GDParser.OperatorNode)op).op == GDParser.OperatorNode.Operator.OP_INDEX_NAMED) 
                                {
							        set_index = codegen.get_name_map_pos(((GDParser.IdentifierNode)op.arguments[1]).name);
							        is_named = true;
						        } 
                                else
                                {

							        set_index = _parse_expression(ref codegen,op.arguments[1], slevel + 1);
							        is_named = false;
						        }


						        if (set_index<0)
							        return set_index;

						        if ((set_index & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                {
							        slevel++;
							        codegen.alloc_stack(slevel);
						        }


						        int set_value = _parse_assign_right_expression(ref codegen, on, slevel+1);
						        if (set_value<0)
							        return set_value;

						        codegen.opcodes.Add(is_named ? (int)GDFunction.Opcode.OPCODE_SET_NAMED : (int)GDFunction.Opcode.OPCODE_SET);
						        codegen.opcodes.Add(prev_pos);
						        codegen.opcodes.Add(set_index);
						        codegen.opcodes.Add(set_value);

						        for(int i = 0; i < setchain.Count; i += 4) 
                                {
							        codegen.opcodes.Add(setchain[i + 0]);
							        codegen.opcodes.Add(setchain[i + 1]);
							        codegen.opcodes.Add(setchain[i + 2]);
							        codegen.opcodes.Add(setchain[i + 3]);
						        }

						        return retval;
                            }
                            else
                            {
                                //ASSIGNMENT MODE!!
						        int slevel = p_stack_level;

						        int dst_address_a = _parse_expression(ref codegen, on.arguments[0], slevel);
						        if (dst_address_a < 0)
							        return -1;

						        if ((dst_address_a & ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)) != 0) 
                                {
							        slevel++;
							        codegen.alloc_stack(slevel);
						        }

						        int src_address_b = _parse_assign_right_expression(ref codegen, on, slevel);
						        if (src_address_b<0)
							        return -1;


						        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_ASSIGN); // perform operator
						        codegen.opcodes.Add(dst_address_a); // argument 1
						        codegen.opcodes.Add(src_address_b); // argument 2 (unary only takes one parameter)
						        return dst_address_a; //if anything, returns wathever was assigned or correct stack position
                            }
                        } break;

                        default: {
                            //ERR_EXPLAIN("Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression.");
                            return 0; //unreachable code
                        } break;
                    }
                    
                    int dst_addr = (p_stack_level) | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);
			        codegen.opcodes.Add(dst_addr); // append the stack level as destination address of the opcode
			        codegen.alloc_stack(p_stack_level);
			        return dst_addr;
                } break;

                default: {
                    //ERR_EXPLAIN("Bug in bytecode compiler, unexpected node in parse tree while parsing expression.");
                    return -1; //unreachable code
                } break;
            }
            return -1;
        }
    }
}
